package cn.bytepulse.utils.ffmpeg;

import cn.bytepulse.utils.ffmpeg.config.properties.FfmpegProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;

/**
 * @author jiejiebiezheyang
 * @since 2025-03-30 22:00
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class Ffmpeg {

    private final FfmpegProperties ffmpegProperties;

    /**
     * 处理视频的转码
     *
     * @param inputFile    文件
     * @param outputFolder 输出目录
     */
    public void transcodeVideo(File inputFile, String outputFolder) throws IOException, InterruptedException {
        // 检查Ffmpeg是否存在
        if (!isFfmpegInstalled()) {
            log.info("❌ Ffmpeg 未安装，请先安装！喵~ (╥﹏╥)");
            return;
        }

        String name = inputFile.getName();
        if (!name.endsWith(".mp4")) {
            throw new IOException("非视频文件");
        }

        File outputDir = new File(outputFolder);

        // 确保输出目录存在
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }

        // 视频文件
        processFile(inputFile, outputDir);

        log.info("✅ 批量 HLS 转码完成！喵~ (ฅ^･ω･^ฅ)");
    }

    /**
     * 检查 Ffmpeg 和 Ffprobe 是否已安装
     */
    private boolean isFfmpegInstalled() {
        try {
            // 检查 Ffmpeg 是否安装
            ProcessBuilder ffmpegBuilder = new ProcessBuilder(ffmpegProperties.getFfmpegPath(), "-version");
            Process ffmpegProcess = ffmpegBuilder.start();
            int ffmpegExitValue = ffmpegProcess.waitFor();

            // 检查 Ffprobe 是否安装
            ProcessBuilder ffprobeBuilder = new ProcessBuilder(ffmpegProperties.getFfprobePath(), "-version");
            Process ffprobeProcess = ffprobeBuilder.start();
            int ffprobeExitValue = ffprobeProcess.waitFor();

            // 返回 Ffmpeg 和 Ffprobe 是否都正常安装
            return ffmpegExitValue == 0 && ffprobeExitValue == 0;

        } catch (IOException | InterruptedException e) {
            return false;
        }
    }

    /**
     * 是否支持NVENC
     */
    public boolean isNVENCSupported() {
        try {
            // 执行 ffmpeg -hwaccels 命令
            Process process = new ProcessBuilder(ffmpegProperties.getFfmpegPath(), "-hwaccels").start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            boolean hasNvidia = false;
            // 逐行读取输出，判断是否包含 cuda / nvenc / nvdec
            while ((line = reader.readLine()) != null) {
                if (line.contains("cuda") || line.contains("nvenc") || line.contains("nvdec")) {
                    hasNvidia = true;
                    break;
                }
            }
            process.waitFor();
            return hasNvidia;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 处理视频文件
     *
     * @param inputFile 文件
     * @param outputDir 输出目录
     */
    private void processFile(File inputFile, File outputDir) throws IOException, InterruptedException {
        // 获取视频信息（分辨率、比特率等）
        int resolutionHeight = getResolutionHeight(inputFile);
        int originalBitrate = getOriginalBitrate(inputFile);
        int originalAudioBitrate = getOriginalAudioBitrate(inputFile);
        // 定义目标比特率和分辨率
        List<Integer> resolutions = Arrays.asList(1080, 720, 360);
        resolutions.stream()
                .filter(res -> resolutionHeight >= res)
                .forEach(res -> {
                    try {
                        processStream(inputFile, outputDir, res, originalBitrate, originalAudioBitrate);
                    } catch (IOException | InterruptedException e) {
                        log.error(e.getMessage());
                    }
                });
        // 生成 master.m3u8 播放列表
        generateMasterPlaylist(outputDir, resolutions);
    }

    /**
     * 获取视频分辨率（高度）
     *
     * @param inputFile 文件
     */
    private int getResolutionHeight(File inputFile) throws IOException, InterruptedException {
        String resolution = executeFFprobeCommand(ffmpegProperties.getFfprobePath(), "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=height", "-of", "csv=p=0", inputFile.getAbsolutePath());
        resolution = resolution.trim();
        resolution = StringUtils.hasText(resolution) ? resolution : "1080";
        return Integer.parseInt(resolution);
    }

    /**
     * 获取视频比特率
     *
     * @param inputFile 文件
     */
    private int getOriginalBitrate(File inputFile) throws IOException, InterruptedException {
        String bitrate = executeFFprobeCommand(ffmpegProperties.getFfprobePath(), "-v", "error", "-select_streams", "v:0", "-show_entries", "format=bit_rate", "-of", "csv=p=0", inputFile.getAbsolutePath());
        bitrate = bitrate.trim();
        bitrate = StringUtils.hasText(bitrate) ? bitrate : "5000000";
        return Integer.parseInt(bitrate) / 1000;
    }

    /**
     * 获取音频比特率
     *
     * @param inputFile 文件
     */
    private int getOriginalAudioBitrate(File inputFile) throws IOException, InterruptedException {
        String audioBitrate = executeFFprobeCommand(ffmpegProperties.getFfprobePath(), "-v", "error", "-select_streams", "a:0", "-show_entries", "stream=bit_rate", "-of", "csv=p=0", inputFile.getAbsolutePath());
        audioBitrate = audioBitrate.trim();
        audioBitrate = StringUtils.hasText(audioBitrate) ? audioBitrate : "256000";
        return Integer.parseInt(audioBitrate.trim()) / 1000;
    }

    /**
     * 执行 ffprobe 命令
     *
     * @param command 命令
     */
    private String executeFFprobeCommand(String... command) throws IOException, InterruptedException {
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();
        process.waitFor();
        String commandString = String.join(" ", command);
        log.info("执行命令: {}", commandString); // 打印执行的命令
        // 使用 BufferedReader 来读取输出
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
                log.info(line);
            }
            return output.toString().trim(); // 返回去掉多余换行的输出
        }
    }

    /**
     * 处理每个视频流
     *
     * @param inputFile            文件
     * @param outputDir            输出目录
     * @param resolution           分辨率
     * @param originalBitrate      视频比特率
     * @param originalAudioBitrate 音频比特率
     */
    private void processStream(File inputFile, File outputDir, int resolution, int originalBitrate, int originalAudioBitrate) throws IOException, InterruptedException {
        String scale = resolution == 1080 ? "1920:1080" : resolution == 720 ? "1280:720" : "640:360";
        String targetBitrate = Math.min(originalBitrate, resolution == 1080 ? 5000 : resolution == 720 ? 2500 : 800) + "k";
        String targetAudioBitrate = Math.min(originalAudioBitrate, 320) + "k";

        String outputFile = new File(outputDir, "stream_" + resolution + ".m3u8").getAbsolutePath();
        String segmentFilename = outputDir + "/stream_" + resolution + "_%03d.ts";

        boolean nvencSupported = isNVENCSupported();

        // 构建并执行命令
        ProcessBuilder builder = new ProcessBuilder(
                ffmpegProperties.getFfmpegPath(), "-i", inputFile.getAbsolutePath(),
                "-vf", "scale=" + scale,
                "-c:v", nvencSupported ? "h264_nvenc" : "libx264",
                "-preset", "fast",
                "-b:v", targetBitrate,
                "-c:a", "aac",
                "-b:a", targetAudioBitrate,
                "-hls_time", "10", "-hls_list_size", "0",
                "-hls_segment_filename", segmentFilename,
                "-f", "hls", outputFile
        );
        // 合并标准输出和错误输出喵~
        builder.redirectErrorStream(true);
        log.info("执行命令: {}", String.join(" ", builder.command()));

        // 执行命令并捕获输出
        Process process = builder.start();

        // 捕获并打印合并后的输出
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                log.info("输出: {}", line);
            }
        }
        // 等待命令执行完毕
        process.waitFor();
    }

    /**
     * 生成 master.m3u8 播放列表
     *
     * @param outputDir   输出目录
     * @param resolutions 输出参数
     */
    private void generateMasterPlaylist(File outputDir, List<Integer> resolutions) throws IOException {
        StringBuilder masterPlaylist = new StringBuilder("#EXTM3U\n");

        for (Integer res : resolutions) {
            masterPlaylist.append("#EXT-X-STREAM-INF:BANDWIDTH=")
                    .append(res == 1080 ? 5000 : res == 720 ? 2500 : 800)
                    .append("000,RESOLUTION=")
                    .append(res == 1080 ? "1920x1080" : res == 720 ? "1280x720" : "640x360")
                    .append("\nstream_")
                    .append(res)
                    .append(".m3u8\n");
        }

        File masterFile = new File(outputDir, "master.m3u8");
        Files.write(masterFile.toPath(), masterPlaylist.toString().getBytes());
    }

    /**
     * 获取视频时长
     *
     * @param videoPath 文件路径
     */
    public long getVideoDurationInSeconds(String videoPath) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(ffmpegProperties.getFfmpegPath(), "-i", videoPath);
            processBuilder.redirectErrorStream(true);

            Process process = processBuilder.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String line;
            while ((line = reader.readLine()) != null) {
                if (line.contains("Duration")) {
                    // 获取时长字符串，例如 "Duration: 00:03:42.12"
                    String duration = line.split(",")[0].split(":")[1].trim();
                    String[] timeParts = duration.split(":"); // 将时长分解为 [小时, 分钟, 秒]

                    int hours = Integer.parseInt(timeParts[0]);
                    int minutes = Integer.parseInt(timeParts[1]);
                    double seconds = Double.parseDouble(timeParts[2]);

                    // 转换为秒
                    return (long) (hours * 3600L + minutes * 60L + seconds);
                }
            }
        } catch (Exception e) {
            return -1;
        }
        return -1;  // 如果出错，返回 -1
    }
}
